package littlecyberwar.tool;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.util.*;
import java.text.DecimalFormat;

import littlecyberwar.model.*;
import littlecyberwar.ui.*;
import littlecyberwar.ui.overlay.*;
import littlecyberwar.util.*;

import ModuleBuilder.model.*;

/**
 * @author Milton  Soong
 *
 * To change the template for this generated type comment go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 * 
 * Note: for inputs we do NOT scale the inputs, just to check the units selected.
 * The amount dragged and such is never scaled with the ScalingDelegate
 */
/**
 * This is the super class which all Handlers inherits from
 */
public class MultiSelectMouseHandler extends MouseHandler {
  InternalCmdFacade model;
  
  /**
   * Make the current element into an array
   */
  static ArrayList selectedElements;
  
  Element currEl;


  Shape oldFinalShape;

  /**
   * This is the original location of the element
   */
  AffineTransform selectedTrans;

  String mapDistanceUnit;
  double mapScale;
//  double pixMoved;  // amount moved during the current operaton
  // static JLabel sideView;
  static CardLayout profileCard;
  // static ProfileCardPanel profilePanel;	//!!! Need to remove after we shift to new multimouse handler

  /**
   * in selected shape's user space. (The screen coordinate has been
   * inversely transformed back to shape's user space.
   */
  Point2D startDragP;

  /**
   * The endDragP is also inversely transformed into the shape's space
   * this is updated as the mouse drags across the screen.
   */
  Point2D endDragP;
  PlayingBoardPanel comp;

  /**
   * The DecimalFormat is used to format the amount of distance moved
   */
  public static DecimalFormat df = new DecimalFormat("0.#");
  
  protected String unitSelectedText ="default unitSelectedText".intern();
  protected String noUnitSelectedText = "default noUnitSelectedText".intern();

  /**
   * Each Handler will have an ID that'll be used as a shorthand to indicate
   * The kind of operation required.
   */
  Integer id = new Integer(-1);

  /**
   * A handler ID
   */
  public final static Integer H_UNDO = new Integer(0);
  public final static Integer H_BACKWARD = new Integer(1);
  public final static Integer H_FREEFORM = new Integer(2);
  public final static Integer H_OBLIQUE = new Integer(3);
  public final static Integer H_PLACEMENT = new Integer(4);
  public final static Integer H_ROTATECENTER = new Integer(5);
  public final static Integer H_ROTATELEFT = new Integer(6);
  public final static Integer H_ROTATERIGHT = new Integer(7);
  public final static Integer H_RULER = new Integer(8);
  public final static Integer H_SIDESTEP = new Integer(9);
  public final static Integer H_STRAIGHTFORWARD = new Integer(10);
  public final static Integer H_WATCH = new Integer(11);
  
  public static HashMap actionStringLookup = new HashMap(); 
  
  static { 
  	actionStringLookup.put(H_BACKWARD, " moved backward ");
	actionStringLookup.put(H_FREEFORM, " freeform moved ");
	actionStringLookup.put(H_OBLIQUE, " moved obliquely ");
	actionStringLookup.put(H_PLACEMENT, " just been placed on the board.");
	actionStringLookup.put(H_ROTATECENTER, " rotated around its center ");
	actionStringLookup.put(H_ROTATELEFT, " rotated left ");
	actionStringLookup.put(H_ROTATERIGHT, " rotated right ");
	actionStringLookup.put(H_SIDESTEP, " side stepped ");
	actionStringLookup.put(H_STRAIGHTFORWARD, " moved straight ");
	actionStringLookup.put(H_WATCH, " watch ");
  }
  
  private Stroke outlineStroke = new BasicStroke(PlayingBoardPanel._DEFAULT_STROKE_WIDTH, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, PlayingBoardPanel._STROKE_WIDTH);
  private Stroke selectionRecOutlineStroke = new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 1);

  
  SelectionState selectionState;

  Point dragSelectStartPoint;
  
  /**
   * The selection rectangle. this is only valid when the selection prosess
   * is goin on. Only when the mouse is pressed down
   *
   */
  Rectangle selectionRectangle;
  
  /**
   * The area that contains a UNION of all selected rectangles of the selected units
   * used for indicating which area needs to be repainted. 
   * 
   * NEW Moved into delegate DirtyAreaManager
   */
  // static Area selectedArea;
  
  /**
   * A lookup table is used to lookup a clicked Element to a 
   * Container.
   * When the selected elements are cleared, this lookup table has to be cleared also
   *
   */
  public static HashMap containerLookup = new HashMap();
  
  /**
   * The overlay related stuff
   *
   */
  private static GlobalOverlayHandler overlayHandler;	
  private static String selectedOverlayId;
  
  /**
   * indicate that the control key is currently held down
   *
   */
  boolean isCntrlSet;

  public MultiSelectMouseHandler() {
	selectedElements = new ArrayList(5);
	selectionState = new SelectionState();
	overlayHandler = new GlobalOverlayHandler(20);
	
	// DEBUG
	selectedOverlayId = "TEST";
  }

	public class SelectionState {
		// see state description on notes page.63
		public static final int IDLE=1;
		public static final int ELEMENTS_SELECTED=2;
		public static final int START_DRAG_SELECT=3;
		public static final int DRAG_SELECT=4;
		public static final int ACTIVE=5;
		
		int currState;
		
		public SelectionState() {
			currState = IDLE;
		}
		
		// called when tools change and there are units already selected
		public void setStateToELEMENTS_SELECTED() {
			if ( !selectedElements.isEmpty() ) {
				currState = ELEMENTS_SELECTED;
			}			
		}
		
		// from here onward all calls are inputs into the state machine
		public void click_space() {
			// //Debug.out("Action=ClickSpace, state=" + currState);
			clearOutSelectionRectangle(true);
			switch ( currState ){
				case IDLE:
					break;
					
				case ELEMENTS_SELECTED:
				case START_DRAG_SELECT:
					// in this case, nothing is selected, return to IDLE
					resetSelectedElements();	
					currState = IDLE;
					break;
					
				case DRAG_SELECT:
				case ACTIVE:
					//Debug.out("Illegal state");	
					break;
			}
		}
		
		public void click_element(Element clickedElement){
			//Debug.out("Action=click_element, state=" + currState);
			switch ( currState ){
				case DRAG_SELECT:
				case ACTIVE:
					//Debug.out("Illegal state");	
					break;					

				case IDLE:
				case ELEMENTS_SELECTED:
					// for some reason this state is possible, just add this selected element
				case START_DRAG_SELECT:
					// one and only one element is selected, so 
					// make it the only selected element, and then change state
					// to work around the fact that one might get a mouse up as well as a 
					// mouse clicked, use add to instead.
					addElementToSelected(clickedElement); 
					currState = ELEMENTS_SELECTED;
					break;
			}
			return;
		}
		
		public void shift_click_element(Element clickedElement){
			//Debug.out("Action=shift_click_element, state=" + currState);
			switch ( currState ){
				case DRAG_SELECT:
				case ACTIVE:
					//Debug.out("Illegal state");	
					break;					

				case ELEMENTS_SELECTED:
				case IDLE:
				case START_DRAG_SELECT:
					// add the selected element to the list						
					addElementToSelected(clickedElement); 
					currState = ELEMENTS_SELECTED;
					break;
			}
		}
		
		public void mouse_down(MouseEvent e, Element clickedElement) {
			//Debug.out("Action=mouse_down, state=" + currState);
			
			//Debug.out("mouse_down: currNum of selected elements = " + selectedElements.size() );

			switch ( currState ){
				case IDLE:
					// now it's time to see if we are drag selecting a bunch of units
					// dragSelectStartPoint = ScalingDelegate.getMouseEventLocation(e.getPoint());
					dragSelectStartPoint = ScalingDelegate.getMouseEventLocation(e.getPoint());
					currState = START_DRAG_SELECT;
					break;

				case ELEMENTS_SELECTED:
					// first check if already selected,
					// if not add it, if so, bring it to the FIRST element
					boolean alreadySelected = checkIfAlreadySelected(clickedElement);
					if ( alreadySelected == true ){
						// start dragging the selected elements
						makeClickedElementActive(clickedElement);
						dragSelectStartPoint = ScalingDelegate.getMouseEventLocation(e.getPoint());
						currState = ACTIVE;	
						// Debug.out("startAction: currNum of selected elements = " + selectedElements.size() );
						startAction(e);				
	
					} else {
						// either clicked space or some other unit, 
						// in which case clean existing elements
						resetSelectedElements();
						dragSelectStartPoint = ScalingDelegate.getMouseEventLocation(e.getPoint());
						currState = START_DRAG_SELECT;
					}						 
					break;
					
				default:
					// illegal state, should've seen a mouseUp first
					//Debug.out("Ilegal state");
					break;	
			}

		}
		
		public void shift_mouse_down(MouseEvent e, Element clickedElement) {
			//Debug.out("Action=shift_mouse_down, state=" + currState);
			switch ( currState ){
				case IDLE:
					// now it's time to see if we are drag selecting a bunch of units
					dragSelectStartPoint = ScalingDelegate.getMouseEventLocation(e.getPoint());
					currState = START_DRAG_SELECT;
					break;

				case ELEMENTS_SELECTED:
					dragSelectStartPoint = ScalingDelegate.getMouseEventLocation(e.getPoint());
					boolean alreadySelected = checkIfAlreadySelected(clickedElement);
					if ( alreadySelected == true ){
						// start dragging
						makeClickedElementActive(clickedElement);
						currState = ACTIVE;					
//						dragSelectStartPoint = p;	
						startAction(e);				
	
					} else {
						// shift clicked on another unit or space
						// if in space, might be another drag select				
						currState = START_DRAG_SELECT;	
					}				
					break;
				
				default:
					// illegal state, should've seen a mouseUp first
					//Debug.out("Ilegal state: START_DRAG_SELECT meets mouse_down");
					break;	
			}
		}
		
		public UnitCommand mouse_up(MouseEvent e) {
			UnitCommand cmd = null;
			
			//Debug.out("Action=mouse_up, state=" + currState);
			switch ( currState ){
				case IDLE:
				case ELEMENTS_SELECTED:
					//Debug.out("Ilegal state: ELEMENTS_SELECTED meets mouse_up");					
					break;

				case START_DRAG_SELECT:
					resetSelectedElements();
					currState = IDLE;
					break;
					
				case DRAG_SELECT:
					/**
					 * the dragSelect rectangle is now determined, 
					 * Find all the elements included in the rectangle, and Add
					 * all of them to the selected element list
					 */
					processDragSelectRectangle(e);
					if ( selectedElements.isEmpty()){
						currState = IDLE;
					} else {
						currState = ELEMENTS_SELECTED;
					}
					break;
					
				case ACTIVE:
					// finished dragging around elements. call stopAction
					currState = ELEMENTS_SELECTED;
					cmd = stopAction();						
			}
			return ( cmd );
		}
		
		public UnitCommand shift_mouse_up(MouseEvent e){
			UnitCommand cmd = null;
			
			//Debug.out("Action=shift_mouse_up, state=" + currState);
			switch ( currState ){
				case IDLE:
				case ELEMENTS_SELECTED:
					//Debug.out("Ilegal state: ELEMENTS_SELECTED meets mouse_up");					
					break;

				case START_DRAG_SELECT:
					// resetSelectedElements();
					currState = IDLE;
					break;
					
				case DRAG_SELECT:
					/**
					 * the dragSelect rectangle is now determined, 
					 * Find all the elements included in the rectangle, and Add
					 * all of them to the selected element list
					 */
					processDragSelectRectangle(e);
					if ( selectedElements.isEmpty()){
						currState = IDLE;
					} else {
						currState = ELEMENTS_SELECTED;
					}
					break;
					
				case ACTIVE:
					// finished dragging around elements. call stopAction
					currState = ELEMENTS_SELECTED;
					cmd = stopAction();						
			}
			return ( cmd );
		}
		
		public void mouse_drag(MouseEvent e){
			//Debug.out("Action=mouse_drag, state=" + currState);
			switch ( currState ){
				case IDLE:
					// illegal state, should have a mouse down first
					//Debug.out("Ilegal state: IDLE meets drag");					
					break;

				case ELEMENTS_SELECTED:
					// do the mouseDragged action
					mouseDragged(e);					
					break;
				
				case START_DRAG_SELECT:
				case DRAG_SELECT:
					/**
					 * Update the drag select rectangle
					 */
					updateDragSelectRectangle(e);
					currState = DRAG_SELECT;
					break;
					
				case ACTIVE:
					mouseDraggedCore(e);
					break;
			}
			
		}
		
	} //public class SelectionState

	
	
	/**
	 * 1. get the current list of selected elements, build a bounds rectangle
	 * 2. clear the arraylist
	 * 3. repaint the bounds rectangle (to "erase" the selected rectangles)
	 */	
	public void resetSelectedElements() {
		// Debug.out("resetSelectedElements");
		// set the profilePanel to the null state also
		profilePanel.noUnitSelected();
		resetViewAfterUnitUnselected();

		// now the area contains the sum of all the shapes
		selectedElements.clear();
		containerLookup.clear();
		
		// OVERLAY now get all the overlay that needs to be cleared out
		overlayHandler.removeAllElements();
//		Rectangle overlayChangedArea = overlayHandler.getAreaChanged();
//		
//		if ( selectedArea != null ){
//			//Debug.out("resetSelectedElements: Painting out the area...");
//			Rectangle rec = selectedArea.getBounds();
//			rec.add(overlayChangedArea);
//			selectedArea = null;
//			rec.grow(PlayingBoardPanel._DEFAULT_STROKE_WIDTH, PlayingBoardPanel._DEFAULT_STROKE_WIDTH);		
//			comp.repaint(rec);
//		}
		
		DirtyAreaManager.checkAndRepaint(comp);
		DirtyAreaManager.reset();
		this.setNoUnitSelectedHelpText();
		return;
	}
	
	/**
	 * One and only one element selected, make it so
	 * @param clickedElement
	 */
	public void oneElementSelected(Element clickedElement){
		Debug.out("oneElementSelected start: numElement selected= "+ selectedElements.size());
		resetSelectedElements();
		SelectedElementContainer selcont = new SelectedElementContainer(clickedElement);

		selectedElements.add(0, selcont);
		containerLookup.put(clickedElement, selcont);
		
		Rectangle rec= clickedElement.getFinalShape().getBounds();
	
		// Overlay stuff
		overlayHandler.addElement(clickedElement);
//		Rectangle overlayChangedArea = overlayHandler.getAreaChanged();
//	
//		if ( selectedArea == null ) {
//			selectedArea = new Area(rec);
//		}
//		rec.add(overlayChangedArea);
//		
//		rec.grow(PlayingBoardPanel._DEFAULT_STROKE_WIDTH, PlayingBoardPanel._DEFAULT_STROKE_WIDTH);
//		comp.repaint(rec);
		
		DirtyAreaManager.checkAndRepaint(comp);
		Debug.out("oneElementSelected end: numElement selected= "+ selectedElements.size());

		return;		
	}
	
	/**
	 * 1. Add this to the existing list of selected element
	 * 2. repaint the area to include the latest addition
	 * 
	 * @param clickedElement
	 */
	public void addElementToSelected(Element clickedElement){
		//Debug.out("--- addElementToSelected: num of element= "+ selectedElements.size());
		// do not add again if it's already in the lookup
		if ( containerLookup.get(clickedElement) == null ) {
			SelectedElementContainer selcont = new SelectedElementContainer(clickedElement);
			selectedElements.add(0, selcont);
			containerLookup.put(clickedElement, selcont );
			updateViewAfterUnitSelected(clickedElement);
			// Rectangle rec = clickedElement.getRectangle().getBounds();
			DirtyAreaManager.addDirtyArea(clickedElement.getFinalShape());
			
			// Overlay stuff
			overlayHandler.addElement(clickedElement);
//			Rectangle overlayChangedArea = overlayHandler.getAreaChanged();
//
//			if ( selectedArea == null ) {
//				selectedArea = new Area();
//			}
//			
//			Area a = new Area(clickedElement.getFinalShape().getBounds());
//			//Debug.out("--- add area for element="+ a.toString());
//			selectedArea.add(a);
//			Rectangle rec = selectedArea.getBounds();
//
//			rec.add(overlayChangedArea);
//
//			rec.grow(PlayingBoardPanel._DEFAULT_STROKE_WIDTH, PlayingBoardPanel._DEFAULT_STROKE_WIDTH);
//			comp.repaint(rec);
			
			DirtyAreaManager.checkAndRepaint(comp);
			this.setUnitSelectedHelpText();
		}
		return;
	}
	
	public void addAllElementAsSelected( ArrayList listIn ) {
//		if ( selectedArea == null ) {
//			selectedArea = new Area();
//		}
		int size = listIn.size();
		for ( int i=0; i < size; i ++ ){
			Element el = (Element)listIn.get(i);
			SelectedElementContainer selcont = new SelectedElementContainer(el);
			selectedElements.add(0, selcont);
			containerLookup.put(el, selcont );
//			Area a = new Area(el.getFinalShape().getBounds());
//			selectedArea.add(a);
			DirtyAreaManager.addDirtyArea(el.getFinalShape());
		}
		
		// Overlay stuff (NEED TO FIX)
		overlayHandler.addElements(listIn);
//		Rectangle overlayChangedArea = overlayHandler.getAreaChanged();
//
//		Rectangle rec = selectedArea.getBounds();			
//		rec.add(overlayChangedArea);
//		
//		rec.grow(PlayingBoardPanel._DEFAULT_STROKE_WIDTH, PlayingBoardPanel._DEFAULT_STROKE_WIDTH);
//		comp.repaint(rec);
		
		DirtyAreaManager.checkAndRepaint(comp);
		this.setUnitSelectedHelpText();
		return;
	}
	
	/**
	 * Loop through the selectedElements and see if already present
	 * 
	 * @param clickedElement
	 * @return true if already in the selectedElements arraylist
	 */
	public boolean checkIfAlreadySelected(Element clickedElement){
		boolean val = false;
		if ( clickedElement == null ) { 
			return val;
		}		
		for ( int i = 0; i < selectedElements.size(); i++){
			Element el = ((SelectedElementContainer)selectedElements.get(i)).element;
			if ( clickedElement == el ){
				val = true;
				break;			
			}
		}
		return ( val );
	}
	
	public void makeClickedElementActive(Element clickedElement) {
		// Debug.out("makeClickedElementActive start: numElement selected= "+ selectedElements.size());	
		SelectedElementContainer selElCont = (SelectedElementContainer)containerLookup.get(clickedElement);
		selectedElements.remove(selElCont);
		selectedElements.add(0, selElCont);
		updateViewAfterUnitSelected(clickedElement);
		// Debug.out("makeClickedElementActive end: numElement selected= "+ selectedElements.size());
	}
	
	/**
	 * The user is now in the process of dragging the selection rectangle
	 * 1. update the rectangle 
	 * 3. repaint the rectangle area.
	 * 
	 * @param endDragSelectPoint
	 */
	public void updateDragSelectRectangle( MouseEvent e ){
		//Debug.out("---- updateDragSelectRectangle");
		// first delete the old rectangle area
		Point endDragSelectPoint = ScalingDelegate.getMouseEventLocation(e.getPoint());
		clearOutSelectionRectangle(false);
		
		// next update the new rectangle
		int x = endDragSelectPoint.x - (int)dragSelectStartPoint.x;
		int y = endDragSelectPoint.y - (int)dragSelectStartPoint.y;
		// Dimension d = new Dimension(x, y);
		
		// for start point, always use the smallest coordibate so that it's on the top left
		int startX = Math.min(endDragSelectPoint.x, (int)dragSelectStartPoint.x);
		int startY = Math.min(endDragSelectPoint.y, (int)dragSelectStartPoint.y);
		
		selectionRectangle = new Rectangle(startX, startY, Math.abs(x), Math.abs(y) );
		Rectangle currRec = selectionRectangle;
		
//		DirtyAreaManager.addDirtyArea(currRec);
//		DirtyAreaManager.checkAndRepaint(comp);
//		DirtyAreaManager.reset();
//		DirtyAreaManager.addDirtyArea(currRec);
		
		Rectangle scaledRectangle = ScalingDelegate.getScaledRectangle(currRec);
		scaledRectangle.grow(PlayingBoardPanel._DEFAULT_STROKE_WIDTH, PlayingBoardPanel._DEFAULT_STROKE_WIDTH);		
		comp.repaint(scaledRectangle); 
		return;
	}
	
	public void clearOutSelectionRectangle(boolean reset) {
		if ( selectionRectangle != null ) {	
			//Debug.out("------- updateDragSelectRectangle: Paint out the old selectionRectangle");
			Rectangle r = selectionRectangle;
			if ( reset ){
				selectionRectangle = null;
			}
			Rectangle scaledRectangle = ScalingDelegate.getScaledRectangle(r);
			scaledRectangle.grow(PlayingBoardPanel._DEFAULT_STROKE_WIDTH, PlayingBoardPanel._DEFAULT_STROKE_WIDTH);		
//			r.grow(5, 5);
//			DirtyAreaManager.addDirtyArea(r);
			comp.repaint(scaledRectangle);
//			DirtyAreaManager.checkAndRepaint(comp);
//			DirtyAreaManager.reset();
		} 		
	}

	/**
	 * clean up
	 * 
	 * @param endDragSelectPoint
	 */
	public void processDragSelectRectangle( MouseEvent e ){
		// the selectionRectangle now contains the final shape
		// now go through ALL the elements out there, and check to 
		// see which ones are completely enclosed by the rectangle.
		// 1. loop thru all element rectangles
		// 2. compare if completely contained in the rectangle
		// 3. if so, add it to the selected elements
		// 4. now set selectionRectangle to NULL
		// 5. repaint the whole area.
		
		Point endP = ScalingDelegate.getMouseEventLocation(e.getPoint());
		
		// based on dragSelectStartPoint and endP, get the selection shape		
		selectionRectangle = new Rectangle(dragSelectStartPoint);
		selectionRectangle.add(endP);
		
		ArrayList allSelectedElements = LayeredUnitHandler.checkAreaSelection(selectionRectangle);
		Element el=null;
		for ( int i=0; i < allSelectedElements.size(); i++ ){
			el = (Element)allSelectedElements.get(i);
			addElementToSelected(el);	        			
		}
		if ( el != null){
			updateViewAfterUnitSelected(el);
		}
		
		// if ( !selectedElements.isEmpty() ) {
		// Need this section here because no command is issued if No pixel is moved
		// thus we have ghost selection outline if you change selection w/o changing the units position.
		// we've changed selected element, need to repaint the board
		clearOutSelectionRectangle(true);
		startDragP = null;
		//}
		return;
	}

	/**
	 * Called by the repaint call from the gameboard to repaint just the selection rectangle
	 * @return
	 */
	public void paintSelectionRectangle(Graphics2D g2) {
		if ( selectionRectangle != null ){
			g2.setStroke( outlineStroke );
			g2.setColor(Color.red);
			g2.draw(selectionRectangle);
		}
		return;
	}

///////////////////////////////////////////////////////////////////////////////
	
  public Integer getId () {
    return ( id );
  }

  public void setBoardPanel(PlayingBoardPanel c) {
    comp = c;
    sideView = c.getSideViewLabel();
  }
  
  public static void setProfilePanelCard( CardLayout in ){
  	profileCard = in;
  }
  
  public static void setProfilePanel( ProfileCardPanel in ){
  	profilePanel = in;
  }

  public void setModelFacade(InternalCmdFacade m) {
    model = m;
    mapScale = Util.getMapScale();
    mapDistanceUnit = Util.getMapDistanceUnit();
  }

  /**
   * IMPORTANT: only to be overwritten if the placement handler is selected
   */
  public void setElement(Element el) {
  }
  
	/**
	 * This is called when the tool become selected in case a particular tool needs to do
	 * Some preprocessing (i.e. for placement tool we might already have a selected unit from the trays
	 * to be overwritten by subclass.
	 */	 
  public void onToolSelect() {
  }

/**
 * The new Multiple element select friendly processMouseEvent
 * 
 * All mouse down will come here, and then will follow what happens on 
 * the state diagram on notebook page.61 
 * 
 * @param e the mouse event corresponding to the current user action
 */
	public void processMouseDown( MouseEvent e ) {
		
	} 
	
	
  /**
   * Now it depends on the following conditions, and a call into the 
   * SelectionState state machine is then determined.
   * 
   * mouse_down
   * shift_mouse_down
   */
  public boolean checkSelection( MouseEvent e ) {
  	//Debug.out("checkSelection: mouse down");
  	Point p = ScalingDelegate.getMouseEventLocation(e.getPoint());
	int onmask = InputEvent.SHIFT_DOWN_MASK;
	Element el = checkElementSelected(p);
		
  	if ((e.getModifiersEx() & onmask) == onmask) {
  		// the shift key is held down, check for multi-select
  		selectionState.shift_mouse_down(e, el);
  		return ( false );
  		
  	} else {
  		selectionState.mouse_down(e, el);
  	}
  	return ( false );

  }

	void resetSelectedElement(){
		startDragP = null;
		for ( int i=0; i< selectedElements.size(); i++){
			SelectedElementContainer elcont = (SelectedElementContainer)selectedElements.get(i);
			elcont.selectedRectangle = null;
		}		
		selectedTrans = null;
		oldFinalShape = null;
		//System.out.println("selectedTrans set to null...");
		currTrans = null;
		currEl = null;
		this.setNoUnitSelectedHelpText();
	}
	
	public void resetSelectedElement(Element el){
		boolean isAlreadySelected = checkIfAlreadySelected(el); 
		if ( isAlreadySelected ) {
			startDragP = null;
			SelectedElementContainer elcont = (SelectedElementContainer)containerLookup.remove(el);
			elcont.selectedRectangle = null;
			selectedElements.remove(elcont);
			
			DirtyAreaManager.checkAndRepaint(comp);
//			if ( selectedArea != null ){				
//				Rectangle rec = selectedArea.getBounds();			
//				rec.grow(PlayingBoardPanel._DEFAULT_STROKE_WIDTH, PlayingBoardPanel._DEFAULT_STROKE_WIDTH);
//				comp.repaint(rec);
//			}
		}
		return;		
	}
	
	/**
	 * Have to handle the following conditions, and then generate event into the
	 * state machine
	 * 
	 * click_space
	 * click_element
	 * shift_click_element
	 */
	public void mouseClicked( MouseEvent e ){
		//Debug.out("mouseClicked");
		Point p = ScalingDelegate.getMouseEventLocation(e.getPoint());
		
		int onmask = InputEvent.SHIFT_DOWN_MASK;	
		if ((e.getModifiersEx() & onmask) == onmask) {
			// the shift key is held down, check for multi-select
			Element el = checkElementSelected(p);
			if ( el != null ) {
				selectionState.shift_click_element(el);
			}
		} else {
			Element el = checkElementSelected(p);
			if ( el != null ){
				selectionState.click_element(el);
			} else {
				selectionState.click_space();
			}
		}
		return;		
	}
	
	public Element checkElementSelected( Point p ){
		Element el;
		el = LayeredUnitHandler.checkElementSelected(p);
		if ( el != null ){
			updateViewAfterUnitSelected(el);
		}
		return ( el );
	}

  // update all view with the current selected unit
  public void updateViewAfterUnitSelected(Element el){
	ImageIcon sideImage = el.getSideImage();
	sideView.setIcon(sideImage);
	// sideView.setText(currEl.getName());
	profilePanel.setUnit(el.getUnit());
  }
  
  public void startAction( MouseEvent e ) {
    pixMoved = 0;
    comp.printToStatus( unitSelectedText);
  }

  public UnitCommand mouseUp( MouseEvent e ) {
  	Point p = ScalingDelegate.getMouseEventLocation(e.getPoint());
	int onmask = InputEvent.SHIFT_DOWN_MASK;	
	if ((e.getModifiersEx() & onmask) == onmask) {
		return (selectionState.shift_mouse_up(e));  	
	} else {
		return (selectionState.mouse_up(e));  	
	}
  }
  
  /**
   * First have to handle this:
   * mouse_up
   * 
   * mouse is released. Put the rectangle to the final currTrans
   * and also for the default case keep the selected rectangle
   * intact.
   * The amount that a unit "moves" is also printed out here.
   * At this point the linear distance between startDragP and
   * endDragP is the distance in pixels.
   * 
   * modifying code to return a unit command at this point, and do NOT
   * directly apply the change to the model or element.
   */
  public UnitCommand stopAction() {
  	// Debug.out("stopAction(): mouseReleased");
 
  	MultiUnitCommand multCmd = null;
  	if ( selectedElements.size() > 1){
  		multCmd = new MultiUnitCommand();  	
  	}
  	UnitCommand cmd=null;
	for ( int i=0; i< selectedElements.size(); i++){
		SelectedElementContainer elcont = (SelectedElementContainer)selectedElements.get(i);
		Element el = elcont.element;
		
		if ( elcont.selectedRectangle != null ) {
			// !!! DEBUG HEY the dist variable is not used
			double dist=0;
			if ( (startDragP != null) && (endDragP != null) ) {
				dist= startDragP.distance(endDragP);
				
				// transform distance to the Actual unscaled distance moved
			}
			if ( Math.abs(pixMoved) > 0 ){	
				// Debug.out("el.getElementId()= " + el.getElementId());
				cmd = getUnitCommand(el.getElementId(), id, elcont.currTrans, pixMoved/mapScale);		
				cmd.setPlayerName(Players.getSelfName());
				if ( multCmd != null ){
					multCmd.addUnitCommand(cmd);
				}
				
			} else {
				// Debug.out("( pixMoved <= 0 )");
    	
			}
		} else {
			Debug.out("( elcont.selectedRectangle == null )");
		}
		// clean up container
		  //elcont.resetElementContainer();

	}
	if (multCmd != null ){
		cmd = multCmd;
	}
	return ( cmd );      
  }

  /**
   * Separated out so it can be override by subclass
   *
   */
  protected UnitCommand getUnitCommand(Integer elementId, Integer handlerId, AffineTransform trans, double pixMoved) {
  	UnitCommand cmd = new UnitCommand(elementId, handlerId, trans, pixMoved);
  	return ( cmd );
  }
  
  public void mouseDragged( MouseEvent e ) {
	selectionState.mouse_drag(e); 	
  }
  
	public void mouseDraggedCore( MouseEvent e ) {
    if ( selectedElements.size() > 0 ){
    	Point newP = ScalingDelegate.getMouseEventLocation(e.getPoint());
	  
		/**
		 * Need to save away the old selected area because dragSelected
		 * Will update that. We want to repaint the union of both of these 
		 * so there are no artifact on screen.
		 */
		// Area oldSelectedArea = selectedArea;
		Rectangle  oldSelectedRec = DirtyAreaManager.getDirtyArea();
		DirtyAreaManager.reset();
		
		// check to see if the control key is held (might change how rotate is handled
		//
		int onmask = InputEvent.CTRL_DOWN_MASK;	
		if ((e.getModifiersEx() & onmask) == onmask) {
			isCntrlSet = true;
		} else {
			isCntrlSet = false;
		}

		dragSelected(newP);
		//!! need to use bounds check to select just changed area
		// if ( (startDragP != null) && (endDragP != null) && (pixMoved != 0) ) {
		printToStatus("Unit tentatively moved: " + df.format(pixMoved/mapScale) + " " + mapDistanceUnit );
		// }
		
		Rectangle  newSelectedRec = DirtyAreaManager.getDirtyArea();
		
		// now repaint the total of new and old changed area
		//
		Area totalArea = new Area(newSelectedRec);
		totalArea.add(new Area(oldSelectedRec));
		DirtyAreaManager.checkAndRepaint(comp, totalArea);
    }
  }
	
	/**
	 * To be overriden by subclass
	 *
	 */
  protected void printToStatus( String val ){
  	comp.printToStatus( val );
  }

  protected void dragSelected( Point newP ) {
	// override by subclass
  }

  // whenever any of the handler has modified the state of the model,
  // need to fire this so all VIEWS gets updated
  public void modelChanged() {
    model.modelChanged();
  }

//  public void modelChanged(Rectangle r) {
//    model.modelChanged(r);
//  }

  public void setNoUnitSelectedHelpText () {
    comp.printToStatus(noUnitSelectedText);
  }

  public void setUnitSelectedHelpText() {
    comp.printToStatus(unitSelectedText);
  }

	/**
	 * Each of the tool gets called here to do its own painting of 
	 * units and other overlays
	 * 
	 * @param g the graphics2D context passed in from the main PlayingBoardPanel
	 */
	public void paintStuff(Graphics2D g2) {
		// ScalingDelegate.adjustScale(g2);
		// paint all the selectedRectangle of all the selected element
		//
		for ( int i=0; i< selectedElements.size(); i++){
			SelectedElementContainer elcont = (SelectedElementContainer)selectedElements.get(i);
			Shape s= elcont.selectedRectangle;
			g2.setStroke( outlineStroke );
			g2.setColor(Color.red);
			g2.draw(s);			
		}
				
		// draw overlay
		overlayHandler.paintStuff(g2);

		// draw the outline of the selection rectangle
		// the selectyion rectangle should be drawn unscaled
		//
		// ScalingDelegate.resetScale(g2);
		
		Shape r = getSelectionRectangle();
		// AlphaComposite ap = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)0.3);
		if ( r != null ) {
		  g2.setStroke( selectionRecOutlineStroke );
		  g2.setColor(Color.red);
		  g2.draw(r);
		  
//		  g2.setComposite(ap);
//		  g2.fill(r);
		}
	}
	
	public void applyRemoteChange(Element elIn) {
		// implemented in sub-class
	}
	
	/**
	 * @return
	 */
	public Rectangle getSelectionRectangle() {
		return selectionRectangle;
	}
	
	/**
	 * @param rectangle
	 */
	public void setSelectionRectangle(Rectangle rectangle) {
		selectionRectangle = rectangle;
	}

/**
 * @return
 */
public static ArrayList getSelectedElements() {
	return selectedElements;
}

/**
 * @param list
 */
public void setSelectedElements(ArrayList list) {
	selectedElements = list;
}

/**
 * @return
 */
public static HashMap getContainerLookup() {
	return containerLookup;
}

/**
 * @param map
 */
public static void setContainerLookup(HashMap map) {
	containerLookup = map;
}

public void handlerSelected(){
	selectionState.setStateToELEMENTS_SELECTED();
	// Debug.out("handlerSelected: numElement selected= "+ selectedElements.size());	
	for ( int i=0; i < selectedElements.size(); i++ ){
		SelectedElementContainer cont = (SelectedElementContainer)selectedElements.get(i);
		cont.resetElementContainer();	
	}
	
	DirtyAreaManager.checkAndRepaint(comp);
//	if ( selectedArea != null ){
//		Rectangle rec = selectedArea.getBounds();			
//		rec.grow(PlayingBoardPanel._DEFAULT_STROKE_WIDTH, PlayingBoardPanel._DEFAULT_STROKE_WIDTH);
//		comp.repaint(rec);
//	}
	return;
}

/**
 * @author Milton  Soong
 *
 * this class is use to hold the elements in the process of being sorted
 * for group rotate.
 */
public class GroupSortElementHolder implements Comparable {
	public SelectedElementContainer cont;
	public double refPointX, refPointY;
		
	public int compareTo(Object o) {
		int val = 0;
		GroupSortElementHolder target = (GroupSortElementHolder)o;
		if ( this.refPointX < target.refPointX ){
			val = -1;
		} else if ( this.refPointX > target.refPointX ){
			val = 1;
		}
		return ( val );
	}
}

public class SecondaryGroupSortElementHolder implements Comparable {
	public SelectedElementContainer cont;
	public double refPointX, refPointY;
		
	public int compareTo(Object o) {
		int val = 0;
		SecondaryGroupSortElementHolder target = (SecondaryGroupSortElementHolder)o;
		if ( this.refPointY < target.refPointY ){
			val = -1;
		} else if ( this.refPointY > target.refPointY ){
			val = 1;
		}
		return ( val );
	}
}

public ArrayList createLeftFrontList( ArrayList elementsIn ) {
	Element refEl = ((SelectedElementContainer)selectedElements.get(0)).element;
			
	AffineTransform refTrans = refEl.getTransform();
	ArrayList resultList = new ArrayList(elementsIn.size());
	
	/**
	 * Now loop through all the selected elements, and then inverse
	 * transform their left front point onto the reference coordinate
	 */
	for ( int i=0; i < selectedElements.size(); i++ ) {
		SelectedElementContainer cont = ((SelectedElementContainer)selectedElements.get(i));
		Element el = cont.element;			  	
		Point2D leftFrontP = el.getFinalLeftFrontPoint();
		Point2D lfpRefCoordinate = null;
		try {
			lfpRefCoordinate = refTrans.inverseTransform(leftFrontP, lfpRefCoordinate);
		} catch ( Exception ex ){
			ex.printStackTrace();
		}
		GroupSortElementHolder holder = new GroupSortElementHolder();
		holder.cont = cont;
		holder.refPointX = lfpRefCoordinate.getX();
		holder.refPointY = lfpRefCoordinate.getY();
				
		resultList.add(holder);
	}
	return( resultList );		
}

/**
 * @return Returns the selectedOverlayId.
 */
public static String getSelectedOverlayId() {
	return selectedOverlayId;
}
/**
 * @param selectedOverlayId The selectedOverlayId to set.
 */
public static void setSelectedOverlayId(String selectedOverlayId) {
	MultiSelectMouseHandler.selectedOverlayId = selectedOverlayId;
}

/**
 * @param recToAdd the rectangular area that's to be added to the area to be repainted
 */
//protected void updateSelectedAreaForRepaint(Rectangle2D recToAdd) {
//	Area a = new Area(recToAdd);
//	selectedArea.add(a);
//}
}